#include <stdlib.h>
#include <stdio.h>
#include <memory.h>

#include "../utils/utils.h"
#include "pbnet.h"

#ifdef WIN32

#include <winsock2.h>
typedef int socklen_t;

#else

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/time.h>

#define closesocket   close
typedef unsigned char BYTE;
typedef int SOCKET;
typedef sockaddr_in SOCKADDR_IN;
typedef timeval TIMEVAL;
typedef hostent HOSTENT;
typedef timeval TIMEVAL;
typedef fd_set FD_SET;

#endif


// FIXME: nagyon faradt voltam, vagy tenyleg nincs jobb megoldas erre? :)

#ifdef USE_POSIX_THREADS
#define THREAD_RETURN       return
typedef void *THREAD_RETURN_TYPE;
#else
#define THREAD_RETURN       sizeof
typedef void THREAD_RETURN_TYPE;
#endif


#define net_printf  printf("<NET>    "); printf
#define cl_printf   printf("<CLIENT> "); printf
#define sv_printf   printf("<SERVER> "); printf

#define MAX_PACKETSIZE  (1024 * 256)
#define UDP_SIZE        20

static int MAX_PACKET      = 1;//1024 * 4;
static int PACKET_SIZE     = 1214;

static const char *HOSTNAME = "127.0.0.1";
static unsigned short HOSTPORT = 1297;


static volatile bool g_ServerReady = false;
static volatile double g_ServerResult = 0;          // ebben tarolja a server az idejet


static THREAD_RETURN_TYPE ServerThread(void *pThreadParam)
{
    SOCKET mysocket;
    struct sockaddr_in myaddr;
    struct sockaddr_in from;    
                        
    if ((mysocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        THREAD_RETURN(0);

    memset(&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(HOSTPORT);
    myaddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(mysocket, (sockaddr*)&myaddr, (int)sizeof(myaddr)) < 0)
    {
        closesocket(mysocket);
        THREAD_RETURN(0);
    }

    static uint8_t buff[1500];
    double time_first_packet=0, time_last_packet=0;     // elso/utolso bejovo packet -> ha a kulonbseg nagy, akkor time out, kilepunk

    g_ServerReady = true;

    for (;;)
    {   
        double current_time = os_GetTime();

        socklen_t fromlen = sizeof(from);

        if (recvfrom(mysocket, (char*)&buff, PACKET_SIZE, 0, (sockaddr*)&from, &fromlen) < 0)
            break;

        //sv_printf("megkaptam: %d\n", *(int*)buff);

        time_last_packet = current_time;

        if (time_first_packet == 0)                     // ez az elso packet?
            time_first_packet = time_last_packet;

        //sv_printf("kuldom: %d\n", *(int*)buff);

        if (sendto(mysocket, (char*)&buff, PACKET_SIZE, 0, (sockaddr*)&from, fromlen) < 0)
            break;

        if (*(int*)buff >= MAX_PACKET - 1)
        {
            g_ServerResult = time_last_packet - time_first_packet;
            break;
        }
    }

    closesocket(mysocket);

    THREAD_RETURN(0);
}


static THREAD_RETURN_TYPE ClientThread(void *pThreadParam)
{
    SOCKET mysocket = 0;
    struct sockaddr_in server;
    struct hostent *he = NULL;    


    if ((mysocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        THREAD_RETURN(0);
        
    if ((he = gethostbyname(HOSTNAME)) == NULL)
    {
        closesocket(mysocket);
        THREAD_RETURN(0);
    }

    memset(&server, 0, sizeof(server));
    server.sin_port = htons(HOSTPORT);
    server.sin_family = AF_INET;

    memcpy(he->h_addr, &server.sin_addr, he->h_length); 
    server.sin_addr.s_addr = inet_addr(HOSTNAME);

    uint8_t buff[1500];

    memset(buff, 0, sizeof(buff));

    for (int i=0; i < MAX_PACKET; i++)        // a biztonsag kedveert megyunk 2*MAX_PACKET -ig (nem azt merjuk, hogy mennyire megbizhato az UDP)
    {   
        *((int*)buff) = i;

        if (sendto(mysocket, (const char*)&buff, PACKET_SIZE, 0, (sockaddr*)&server, (int)sizeof(server)) < 0)
        {
            cl_printf("Nem sikerult az adatot elkuldeni\n");
            break;
        }

        //cl_printf("kuldom: %d\n", i);

        if (recv(mysocket, (char*)&buff, PACKET_SIZE, 0) <= 0)
        {
            cl_printf("Nem sikerult a csomag fogadasa\n");
        }

        //cl_printf("megkaptam: %d\n", *(int*)buff);

        if (*(int*)buff >= MAX_PACKET - 1)
            break;
    }

    closesocket(mysocket);

    THREAD_RETURN(0);
}


static void UdpTest(int packcount, int packsize, IResultWriter *results)
{   
    void *threads = os_CreateThreadVariables(2);

    if (!threads)
        return;

    PACKET_SIZE = packsize;
    MAX_PACKET = packcount;

    // sv inditasa

    g_ServerReady = false;
    g_ServerResult = 0;

    os_StartThread(threads, 0, ServerThread, 0);

    while (!g_ServerReady)
        ;

    // cl inditasa

    os_StartThread(threads, 1, ClientThread, 0);

    os_JoinThread(threads, 0);
    os_JoinThread(threads, 1);

    os_DeleteThreadVariables(threads, 2);

    if (g_ServerResult)
    {
        results->WriteNode(ft("udp_local_%dx%d", packcount, packsize), (float)g_ServerResult);
    }
}



void CNetBenchmark::Run(IResultWriter *results, const CCmdlineArgs *args)
{                   
    if (os_NetInit() == false)
        return;

    const int SIZE = 1024 * 512;
    
    UdpTest(SIZE / 16, 16, results);
    UdpTest(SIZE / 64, 64, results);
    UdpTest(SIZE / 256, 256, results);
    UdpTest(SIZE / 512, 512, results);
    UdpTest(SIZE / 1024, 1024, results);
    UdpTest(SIZE / 1400, 1400, results);
    

    os_NetCleanup();
}

#undef THREAD_RETURN
